package utils

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"fmt"
)

/*CBC加密 按照golang标准库的例子代码
不过里面没有填充的部分,所以补上
*/
//使用PKCS7进行填充，IOS也是7
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

func PKCS7UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

// AesDecrypt ase 解密
// rawData: base64Decode 之后的数据
func AesDecrypt(rawData, key, iv []byte) (result []byte, err error) {
	combineData := bytesCombine(iv, rawData)
	return aesCBCDecrypt(combineData, key)
}

// AesEncrypt ase 加密
// aes加密，填充秘钥key的16位，24,32分别对应AES-128, AES-192, or AES-256.
func AesEncrypt(rawData, key []byte, iv []byte) (cipherText []byte, err error) {
	var block cipher.Block
	if block, err = aes.NewCipher(key); err != nil {
		return
	}
	//填充原文
	blockSize := block.BlockSize()
	if len(iv) != blockSize {
		err = fmt.Errorf("len(iv) != blockSize(len:%d)", blockSize)
		return
	}
	rawData = PKCS7Padding(rawData, blockSize)
	//初始向量IV必须是唯一，但不需要保密
	cipherText = make([]byte, blockSize+len(rawData))
	//golang 默认前 blockSize 字节大小(16),为 iv ,但是这里我们使用自己生成的 iv
	//iv := cipherText[:blockSize]
	// 生成随机 iv 值
	//if _, err = io.ReadFull(rand.Reader, iv); err != nil {
	//	return
	//}
	//block大小和初始向量大小一定要一致
	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(cipherText[blockSize:], rawData)

	cipherText = cipherText[blockSize:]
	return
}

// ase 解密
func aesCBCDecrypt(encryptData, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	blockSize := block.BlockSize()

	if len(encryptData) < blockSize {
		panic("ciphertext too short")
	}
	iv := encryptData[:blockSize]
	encryptData = encryptData[blockSize:]

	// CBC mode always works in whole blocks.
	if len(encryptData)%blockSize != 0 {
		panic("ciphertext is not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)

	// CryptBlocks can work in-place if the two arguments are the same.
	mode.CryptBlocks(encryptData, encryptData)
	//解填充
	encryptData = PKCS7UnPadding(encryptData)
	return encryptData, nil
}

func bytesCombine(pBytes ...[]byte) []byte {
	length := len(pBytes)
	s := make([][]byte, length)
	for index := 0; index < length; index++ {
		s[index] = pBytes[index]
	}
	sep := []byte("")
	return bytes.Join(s, sep)
}
